CM3D2 Converter.misc_OBJECT_PT_transform

  1# 「プロパティ」エリア → 「オブジェクト」タブ → 「トランスフォーム」パネル
  2import bpy
  3import bmesh
  4import mathutils
  5import numpy as np
  6from . import common
  7from . import compat
  8from .translations.pgettext_functions import *
  9from .model_export import CNV_OT_export_cm3d2_model
 10
 11
 12# メニュー等に項目追加
 13def menu_func(self, context):
 14    self.layout.operator('object.sync_object_transform'   , icon_value=common.kiss_icon())
 15    self.layout.operator('object.align_to_cm3d2_base_bone', icon_value=common.kiss_icon())
 16
 17
 18@compat.BlRegister()
 19class CNV_OT_sync_object_transform(bpy.types.Operator):
 20    bl_idname = 'object.sync_object_transform'
 21    bl_label = "オブジェクトの位置を合わせる"
 22    bl_description = "アクティブオブジェクトの中心位置を、他の選択オブジェクトの中心位置に合わせます"
 23    bl_options = {'REGISTER', 'UNDO'}
 24
 25    @classmethod
 26    def poll(cls, context):
 27        obs = context.selected_objects
 28        return len(obs) == 2
 29
 30    def execute(self, context):
 31        #target_ob = context.active_object
 32        #for ob in context.selected_objects:
 33        #    if target_ob.name != ob.name:
 34        #        source_ob = ob
 35        #        break
 36        target_ob, source_ob = common.get_target_and_source_object(context)
 37
 38        if compat.IS_LEGACY:
 39            for area in context.screen.areas:
 40                if area.type == 'VIEW_3D':
 41                    for space in area.spaces:
 42                        if space.type == 'VIEW_3D':
 43                            target_space = space
 44                            break
 45
 46            pre_cursor_location = target_space.cursor_location[:]
 47            try:
 48                target_space.cursor_location = source_ob.location[:]
 49
 50                compat.set_select(source_ob, False)
 51                bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
 52                compat.set_select(source_ob, True)
 53
 54            finally:
 55                target_space.cursor_location = pre_cursor_location[:]
 56        else:
 57            pre_cursor_loc = context.scene.cursor.location[:]
 58            try:
 59                context.scene.cursor.location = source_ob.location[:]
 60
 61                compat.set_select(source_ob, False)
 62                bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
 63                compat.set_select(source_ob, True)
 64
 65            finally:
 66                context.scene.cursor.location = pre_cursor_loc[:]
 67        return {'FINISHED'}
 68
 69
 70
 71@compat.BlRegister()
 72class CNV_OT_align_to_cm3d2_base_bone(bpy.types.Operator):
 73    bl_idname = 'object.align_to_cm3d2_base_bone'
 74    bl_label = "Align to Base Bone"
 75    bl_description = "Align the object to it's armature's base bone"
 76    bl_options = {'REGISTER', 'UNDO'}
 77
 78    scale            = bpy.props.FloatProperty(name="Scale"        , default=   5, min=0.1, max=100, soft_min=0.1, soft_max=100, step=100, precision=1, description="The amount by which the mesh is scaled when imported. Recommended that you use the same when at the time of export.")
 79    is_preserve_mesh = bpy.props.BoolProperty (name="Preserve Mesh", default=True, description="Align object transform, then fix mesh transform so it remains in place.")
 80
 81    items = [
 82        ('ARMATURE'         , "Armature"     , "", 'OUTLINER_OB_ARMATURE', 1),
 83        ('TEXT'             , "Text"         , "", 'FILE_TEXT'           , 2),
 84        ('OBJECT_PROPERTY'  , "Object Data"  , "", 'OBJECT_DATAMODE'     , 3),
 85        ('ARMATURE_PROPERTY', "Armature Data", "", 'ARMATURE_DATA'       , 4),
 86    ]
 87    bone_info_mode = bpy.props.EnumProperty(items=items, name="Bone Data Source", default='OBJECT_PROPERTY', description="This will decide from where the Bone Data is gathered from.")
 88
 89
 90    @staticmethod
 91    def find_base_bone(ob: bpy.types.Object):
 92        arm_ob = ob.find_armature()
 93        if (not arm_ob) and (ob.parent and ob.parent.type == 'ARMATURE'):
 94            arm_ob = ob.parent
 95        
 96        base_bone_name = None
 97        if arm_ob:
 98            base_bone_name = arm_ob.data.get('BaseBone')
 99        if not base_bone_name:
100            base_bone_name = ob.data.get('BaseBone')
101        if not base_bone_name:
102            # TODO : Check for base bone in object name
103            # See model_export.CNV_OT_export_cm3d2_model.export() "BoneData情報読み込み"
104            pass
105        
106        return base_bone_name
107
108    
109    @classmethod
110    def poll(cls, context):
111        ob = context.object
112        if not ob: 
113            return False
114        return cls.find_base_bone(ob) != None
115
116    def invoke(self, context, event):
117        ob = context.object
118
119        # model名とか
120        #ob_names = common.remove_serial_number(ob.name, self.is_arrange_name).split('.')
121        #self.model_name = ob_names[0]
122        #self.base_bone_name = ob_names[1] if len(ob_names) >= 2  else 'Auto'
123
124        # ボーン情報元のデフォルトオプションを取得
125        if "BoneData" in context.blend_data.texts:
126            self.bone_info_mode = 'TEXT'
127        if "BoneData:0" in ob:
128            self.bone_info_mode = 'OBJECT_PROPERTY'
129        arm_ob = ob.find_armature()
130        if (not arm_ob) and (ob.parent and ob.parent.type == 'ARMATURE'):
131            arm_ob = ob.parent
132        if arm_ob:
133            if "BoneData:0" in arm_ob.data:
134                self.bone_info_mode = 'ARMATURE_PROPERTY'
135
136        self.scale = common.preferences().scale
137        return context.window_manager.invoke_props_dialog(self)
138
139    def draw(self, context):
140        ob = context.object
141        arm_ob = ob.find_armature()
142        if (not arm_ob) and (ob.parent and ob.parent.type == 'ARMATURE'):
143            arm_ob = ob.parent
144
145        def _prop_enum_row(layout, data, prop, value, enabled=True):
146            row = layout.row(align=True)
147            name = row.enum_item_name(data, prop, value)
148            icon = row.enum_item_icon(data, prop, value)
149            row.prop_enum(data, prop, value, text=name)
150            row.enabled = enabled
151            return row
152        
153        self.layout.prop(self, 'scale')
154        self.layout.prop(self, 'is_preserve_mesh', icon=compat.icon('MESH_DATA'))
155
156        col = self.layout.column(align=True)
157        col.label(text="Bone Data Source", icon='BONE_DATA')
158        _prop_enum_row(col, self, 'bone_info_mode', 'ARMATURE'         , enabled=bool(arm_ob                                ))
159        _prop_enum_row(col, self, 'bone_info_mode', 'TEXT'             , enabled=bool("BoneData" in context.blend_data.texts))
160        _prop_enum_row(col, self, 'bone_info_mode', 'OBJECT_PROPERTY'  , enabled=bool("BoneData:0" in ob                    ))
161        _prop_enum_row(col, self, 'bone_info_mode', 'ARMATURE_PROPERTY', enabled=bool(arm_ob and "BoneData:0" in arm_ob.data))
162
163    @staticmethod
164    def from_bone_data(ob: bpy.types.Object, bone_data, local_bone_data, base_bone_name, scale=5):
165        
166        base_bone_offset = mathutils.Matrix.Identity(4)
167        for bone in local_bone_data:
168            if bone['name'] == base_bone_name:
169                # When the base bone is in the bind pose data, 
170                # then the entire mesh needs to be offset from the base bone
171                print("Found base bone in local bone data!")
172                print(bone['matrix'])
173                mat = mathutils.Matrix(np.array(bone['matrix']).reshape((4,4)))
174                mat.transpose()
175                mat.translation *= -scale
176                mat.translation = compat.mul(mat.to_3x3().inverted(), mat.translation)
177                pos = mat.translation.copy()
178                
179                mat.transpose()
180                mat.translation = pos
181                #mat.row[3] = (0.0, 0.0, 0.0, 1.0)
182
183                base_bone_offset = mat
184
185        for bone in bone_data:
186            if bone['name'] == base_bone_name:
187                #co = bone['co'].copy()
188                #co.x, co.y, co.z = -co.x, -co.z, co.y
189                #co *= self.scale
190                #ob.location = co
191                #
192                #rot = bone['rot'].copy()
193                #eul = mathutils.Euler((math.radians(90), 0, 0), 'XYZ')
194                #rot.rotate(eul)
195                #ob.rotation_mode = 'QUATERNION'
196                #ob.rotation_quaternion = rot
197
198                parent_mats = []
199                current_bone = bone
200                while current_bone:
201                    local_co_mat  = mathutils.Matrix.Translation(mathutils.Vector(current_bone['co']) * scale)
202                    local_rot_mat = mathutils.Quaternion(current_bone['rot']).to_matrix().to_4x4()        
203                    parent_mats.append(compat.mul(local_co_mat, local_rot_mat))
204                    if current_bone.get('parent_name'):
205                        for b in bone_data:
206                            if b['name'] == current_bone['parent_name']:
207                                current_bone = b
208                                break
209                    elif current_bone.get('parent_index', -1) != -1 :
210                        current_bone = bone_data[current_bone['parent_index']]
211                    else:
212                        current_bone = None
213
214                parent_mats.reverse()
215                mat = mathutils.Matrix()
216                for local_mat in parent_mats:
217                    mat = compat.mul(mat, local_mat)
218
219                mat = compat.mul(mat, base_bone_offset.inverted())
220
221                mat = compat.convert_cm_to_bl_space(mat)
222                mat = compat.convert_cm_to_bl_local_space(mat)
223                ob.matrix_basis = mat
224                break
225
226
227    @staticmethod
228    def from_armature(ob: bpy.types.Object, arm: bpy.types.Armature, base_bone_name):
229        base_bone = arm.bones.get(base_bone_name)
230        mat = base_bone.matrix_local.copy()
231        mat = compat.convert_bl_to_cm_bone_rotation(mat)
232        mat = compat.convert_cm_to_bl_local_space(mat)
233        ob.matrix_basis = mat
234
235
236    def bone_data_report_cancel(self):
237        source_name = self.bl_rna.properties['bone_info_mode']        \
238                      and source_name.enum_items[self.bone_info_mode] \
239                      and source_name.name                            \
240                      or self.bone_info_mode
241        self.report(
242            type    = {'ERROR'},
243            message = f_tip_(
244                "Could not find 'BaseBone' in {source_name} Please add it or change source", 
245                source_name = source_name
246            )
247        )
248        return {'CANCELLED'}
249
250
251    def execute(self, context):
252        ob = context.object
253        arm_ob = ob.find_armature()
254        if (not arm_ob) and (ob.parent and ob.parent.type == 'ARMATURE'):
255            arm_ob = ob.parent
256        
257        base_bone_name = None
258        bone_data = None
259        if self.bone_info_mode == 'ARMATURE':
260            #bone_data = CNV_OT_export_cm3d2_model.armature_bone_data_parser(context, arm_ob)
261            if not 'BaseBone' in arm_ob.data:
262                return bone_data_report_cancel()
263            base_bone_name = arm_ob.data['BaseBone']
264        if self.bone_info_mode == 'TEXT':
265            bone_data_text = context.blend_data.texts["BoneData"]
266            if not 'BaseBone' in bone_data_text:
267                return bone_data_report_cancel()
268            base_bone_name = bone_data_text['BaseBone']
269            bone_data = CNV_OT_export_cm3d2_model.bone_data_parser(l.body for l in bone_data_text.lines)
270            local_bone_data = CNV_OT_export_cm3d2_model.local_bone_data_parser(l.body for l in bone_data_text.lines)
271        elif self.bone_info_mode in ['OBJECT_PROPERTY', 'ARMATURE_PROPERTY']:
272            target = ob if self.bone_info_mode == 'OBJECT_PROPERTY' else arm_ob.data
273            if not 'BaseBone' in target:
274                return bone_data_report_cancel()
275            base_bone_name = target['BaseBone']
276            bone_data = CNV_OT_export_cm3d2_model.bone_data_parser(CNV_OT_export_cm3d2_model.indexed_data_generator(target, prefix="BoneData:"))
277            local_bone_data = CNV_OT_export_cm3d2_model.local_bone_data_parser(CNV_OT_export_cm3d2_model.indexed_data_generator(target, prefix="LocalBoneData:"))
278        
279        old_basis = ob.matrix_basis.copy()
280        if bone_data:
281            self.from_bone_data(ob, bone_data, local_bone_data, base_bone_name, self.scale)
282        else:
283            self.from_armature(ob, arm_ob.data, base_bone_name)
284
285        if self.is_preserve_mesh:
286            new_basis = ob.matrix_basis.copy()
287            ob.matrix_basis = compat.mul(new_basis.inverted(), old_basis)
288            bpy.ops.object.transform_apply(location=True, rotation=True, scale=False)
289            ob.matrix_basis = new_basis
290
291
292        return {'FINISHED'}
293
294        
@compat.BlRegister()
class CNV_OT_sync_object_transform(bpy_types.Operator):
19@compat.BlRegister()
20class CNV_OT_sync_object_transform(bpy.types.Operator):
21    bl_idname = 'object.sync_object_transform'
22    bl_label = "オブジェクトの位置を合わせる"
23    bl_description = "アクティブオブジェクトの中心位置を、他の選択オブジェクトの中心位置に合わせます"
24    bl_options = {'REGISTER', 'UNDO'}
25
26    @classmethod
27    def poll(cls, context):
28        obs = context.selected_objects
29        return len(obs) == 2
30
31    def execute(self, context):
32        #target_ob = context.active_object
33        #for ob in context.selected_objects:
34        #    if target_ob.name != ob.name:
35        #        source_ob = ob
36        #        break
37        target_ob, source_ob = common.get_target_and_source_object(context)
38
39        if compat.IS_LEGACY:
40            for area in context.screen.areas:
41                if area.type == 'VIEW_3D':
42                    for space in area.spaces:
43                        if space.type == 'VIEW_3D':
44                            target_space = space
45                            break
46
47            pre_cursor_location = target_space.cursor_location[:]
48            try:
49                target_space.cursor_location = source_ob.location[:]
50
51                compat.set_select(source_ob, False)
52                bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
53                compat.set_select(source_ob, True)
54
55            finally:
56                target_space.cursor_location = pre_cursor_location[:]
57        else:
58            pre_cursor_loc = context.scene.cursor.location[:]
59            try:
60                context.scene.cursor.location = source_ob.location[:]
61
62                compat.set_select(source_ob, False)
63                bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
64                compat.set_select(source_ob, True)
65
66            finally:
67                context.scene.cursor.location = pre_cursor_loc[:]
68        return {'FINISHED'}
bl_idname = 'object.sync_object_transform'
bl_label = 'オブジェクトの位置を合わせる'
bl_description = 'アクティブオブジェクトの中心位置を、他の選択オブジェクトの中心位置に合わせます'
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
26    @classmethod
27    def poll(cls, context):
28        obs = context.selected_objects
29        return len(obs) == 2
def execute(self, context):
31    def execute(self, context):
32        #target_ob = context.active_object
33        #for ob in context.selected_objects:
34        #    if target_ob.name != ob.name:
35        #        source_ob = ob
36        #        break
37        target_ob, source_ob = common.get_target_and_source_object(context)
38
39        if compat.IS_LEGACY:
40            for area in context.screen.areas:
41                if area.type == 'VIEW_3D':
42                    for space in area.spaces:
43                        if space.type == 'VIEW_3D':
44                            target_space = space
45                            break
46
47            pre_cursor_location = target_space.cursor_location[:]
48            try:
49                target_space.cursor_location = source_ob.location[:]
50
51                compat.set_select(source_ob, False)
52                bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
53                compat.set_select(source_ob, True)
54
55            finally:
56                target_space.cursor_location = pre_cursor_location[:]
57        else:
58            pre_cursor_loc = context.scene.cursor.location[:]
59            try:
60                context.scene.cursor.location = source_ob.location[:]
61
62                compat.set_select(source_ob, False)
63                bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
64                compat.set_select(source_ob, True)
65
66            finally:
67                context.scene.cursor.location = pre_cursor_loc[:]
68        return {'FINISHED'}
bl_rna = <bpy_struct, Struct("OBJECT_OT_sync_object_transform")>
Inherited Members
bpy_types.Operator
as_keywords
poll_message_set
builtins.bpy_struct
keys
values
items
get
pop
as_pointer
keyframe_insert
keyframe_delete
driver_add
driver_remove
is_property_set
property_unset
is_property_hidden
is_property_readonly
is_property_overridable_library
property_overridable_library_set
path_resolve
path_from_id
type_recast
bl_rna_get_subclass_py
bl_rna_get_subclass
id_properties_ensure
id_properties_clear
id_properties_ui
id_data
@compat.BlRegister()
class CNV_OT_align_to_cm3d2_base_bone(bpy_types.Operator):
 72@compat.BlRegister()
 73class CNV_OT_align_to_cm3d2_base_bone(bpy.types.Operator):
 74    bl_idname = 'object.align_to_cm3d2_base_bone'
 75    bl_label = "Align to Base Bone"
 76    bl_description = "Align the object to it's armature's base bone"
 77    bl_options = {'REGISTER', 'UNDO'}
 78
 79    scale            = bpy.props.FloatProperty(name="Scale"        , default=   5, min=0.1, max=100, soft_min=0.1, soft_max=100, step=100, precision=1, description="The amount by which the mesh is scaled when imported. Recommended that you use the same when at the time of export.")
 80    is_preserve_mesh = bpy.props.BoolProperty (name="Preserve Mesh", default=True, description="Align object transform, then fix mesh transform so it remains in place.")
 81
 82    items = [
 83        ('ARMATURE'         , "Armature"     , "", 'OUTLINER_OB_ARMATURE', 1),
 84        ('TEXT'             , "Text"         , "", 'FILE_TEXT'           , 2),
 85        ('OBJECT_PROPERTY'  , "Object Data"  , "", 'OBJECT_DATAMODE'     , 3),
 86        ('ARMATURE_PROPERTY', "Armature Data", "", 'ARMATURE_DATA'       , 4),
 87    ]
 88    bone_info_mode = bpy.props.EnumProperty(items=items, name="Bone Data Source", default='OBJECT_PROPERTY', description="This will decide from where the Bone Data is gathered from.")
 89
 90
 91    @staticmethod
 92    def find_base_bone(ob: bpy.types.Object):
 93        arm_ob = ob.find_armature()
 94        if (not arm_ob) and (ob.parent and ob.parent.type == 'ARMATURE'):
 95            arm_ob = ob.parent
 96        
 97        base_bone_name = None
 98        if arm_ob:
 99            base_bone_name = arm_ob.data.get('BaseBone')
100        if not base_bone_name:
101            base_bone_name = ob.data.get('BaseBone')
102        if not base_bone_name:
103            # TODO : Check for base bone in object name
104            # See model_export.CNV_OT_export_cm3d2_model.export() "BoneData情報読み込み"
105            pass
106        
107        return base_bone_name
108
109    
110    @classmethod
111    def poll(cls, context):
112        ob = context.object
113        if not ob: 
114            return False
115        return cls.find_base_bone(ob) != None
116
117    def invoke(self, context, event):
118        ob = context.object
119
120        # model名とか
121        #ob_names = common.remove_serial_number(ob.name, self.is_arrange_name).split('.')
122        #self.model_name = ob_names[0]
123        #self.base_bone_name = ob_names[1] if len(ob_names) >= 2  else 'Auto'
124
125        # ボーン情報元のデフォルトオプションを取得
126        if "BoneData" in context.blend_data.texts:
127            self.bone_info_mode = 'TEXT'
128        if "BoneData:0" in ob:
129            self.bone_info_mode = 'OBJECT_PROPERTY'
130        arm_ob = ob.find_armature()
131        if (not arm_ob) and (ob.parent and ob.parent.type == 'ARMATURE'):
132            arm_ob = ob.parent
133        if arm_ob:
134            if "BoneData:0" in arm_ob.data:
135                self.bone_info_mode = 'ARMATURE_PROPERTY'
136
137        self.scale = common.preferences().scale
138        return context.window_manager.invoke_props_dialog(self)
139
140    def draw(self, context):
141        ob = context.object
142        arm_ob = ob.find_armature()
143        if (not arm_ob) and (ob.parent and ob.parent.type == 'ARMATURE'):
144            arm_ob = ob.parent
145
146        def _prop_enum_row(layout, data, prop, value, enabled=True):
147            row = layout.row(align=True)
148            name = row.enum_item_name(data, prop, value)
149            icon = row.enum_item_icon(data, prop, value)
150            row.prop_enum(data, prop, value, text=name)
151            row.enabled = enabled
152            return row
153        
154        self.layout.prop(self, 'scale')
155        self.layout.prop(self, 'is_preserve_mesh', icon=compat.icon('MESH_DATA'))
156
157        col = self.layout.column(align=True)
158        col.label(text="Bone Data Source", icon='BONE_DATA')
159        _prop_enum_row(col, self, 'bone_info_mode', 'ARMATURE'         , enabled=bool(arm_ob                                ))
160        _prop_enum_row(col, self, 'bone_info_mode', 'TEXT'             , enabled=bool("BoneData" in context.blend_data.texts))
161        _prop_enum_row(col, self, 'bone_info_mode', 'OBJECT_PROPERTY'  , enabled=bool("BoneData:0" in ob                    ))
162        _prop_enum_row(col, self, 'bone_info_mode', 'ARMATURE_PROPERTY', enabled=bool(arm_ob and "BoneData:0" in arm_ob.data))
163
164    @staticmethod
165    def from_bone_data(ob: bpy.types.Object, bone_data, local_bone_data, base_bone_name, scale=5):
166        
167        base_bone_offset = mathutils.Matrix.Identity(4)
168        for bone in local_bone_data:
169            if bone['name'] == base_bone_name:
170                # When the base bone is in the bind pose data, 
171                # then the entire mesh needs to be offset from the base bone
172                print("Found base bone in local bone data!")
173                print(bone['matrix'])
174                mat = mathutils.Matrix(np.array(bone['matrix']).reshape((4,4)))
175                mat.transpose()
176                mat.translation *= -scale
177                mat.translation = compat.mul(mat.to_3x3().inverted(), mat.translation)
178                pos = mat.translation.copy()
179                
180                mat.transpose()
181                mat.translation = pos
182                #mat.row[3] = (0.0, 0.0, 0.0, 1.0)
183
184                base_bone_offset = mat
185
186        for bone in bone_data:
187            if bone['name'] == base_bone_name:
188                #co = bone['co'].copy()
189                #co.x, co.y, co.z = -co.x, -co.z, co.y
190                #co *= self.scale
191                #ob.location = co
192                #
193                #rot = bone['rot'].copy()
194                #eul = mathutils.Euler((math.radians(90), 0, 0), 'XYZ')
195                #rot.rotate(eul)
196                #ob.rotation_mode = 'QUATERNION'
197                #ob.rotation_quaternion = rot
198
199                parent_mats = []
200                current_bone = bone
201                while current_bone:
202                    local_co_mat  = mathutils.Matrix.Translation(mathutils.Vector(current_bone['co']) * scale)
203                    local_rot_mat = mathutils.Quaternion(current_bone['rot']).to_matrix().to_4x4()        
204                    parent_mats.append(compat.mul(local_co_mat, local_rot_mat))
205                    if current_bone.get('parent_name'):
206                        for b in bone_data:
207                            if b['name'] == current_bone['parent_name']:
208                                current_bone = b
209                                break
210                    elif current_bone.get('parent_index', -1) != -1 :
211                        current_bone = bone_data[current_bone['parent_index']]
212                    else:
213                        current_bone = None
214
215                parent_mats.reverse()
216                mat = mathutils.Matrix()
217                for local_mat in parent_mats:
218                    mat = compat.mul(mat, local_mat)
219
220                mat = compat.mul(mat, base_bone_offset.inverted())
221
222                mat = compat.convert_cm_to_bl_space(mat)
223                mat = compat.convert_cm_to_bl_local_space(mat)
224                ob.matrix_basis = mat
225                break
226
227
228    @staticmethod
229    def from_armature(ob: bpy.types.Object, arm: bpy.types.Armature, base_bone_name):
230        base_bone = arm.bones.get(base_bone_name)
231        mat = base_bone.matrix_local.copy()
232        mat = compat.convert_bl_to_cm_bone_rotation(mat)
233        mat = compat.convert_cm_to_bl_local_space(mat)
234        ob.matrix_basis = mat
235
236
237    def bone_data_report_cancel(self):
238        source_name = self.bl_rna.properties['bone_info_mode']        \
239                      and source_name.enum_items[self.bone_info_mode] \
240                      and source_name.name                            \
241                      or self.bone_info_mode
242        self.report(
243            type    = {'ERROR'},
244            message = f_tip_(
245                "Could not find 'BaseBone' in {source_name} Please add it or change source", 
246                source_name = source_name
247            )
248        )
249        return {'CANCELLED'}
250
251
252    def execute(self, context):
253        ob = context.object
254        arm_ob = ob.find_armature()
255        if (not arm_ob) and (ob.parent and ob.parent.type == 'ARMATURE'):
256            arm_ob = ob.parent
257        
258        base_bone_name = None
259        bone_data = None
260        if self.bone_info_mode == 'ARMATURE':
261            #bone_data = CNV_OT_export_cm3d2_model.armature_bone_data_parser(context, arm_ob)
262            if not 'BaseBone' in arm_ob.data:
263                return bone_data_report_cancel()
264            base_bone_name = arm_ob.data['BaseBone']
265        if self.bone_info_mode == 'TEXT':
266            bone_data_text = context.blend_data.texts["BoneData"]
267            if not 'BaseBone' in bone_data_text:
268                return bone_data_report_cancel()
269            base_bone_name = bone_data_text['BaseBone']
270            bone_data = CNV_OT_export_cm3d2_model.bone_data_parser(l.body for l in bone_data_text.lines)
271            local_bone_data = CNV_OT_export_cm3d2_model.local_bone_data_parser(l.body for l in bone_data_text.lines)
272        elif self.bone_info_mode in ['OBJECT_PROPERTY', 'ARMATURE_PROPERTY']:
273            target = ob if self.bone_info_mode == 'OBJECT_PROPERTY' else arm_ob.data
274            if not 'BaseBone' in target:
275                return bone_data_report_cancel()
276            base_bone_name = target['BaseBone']
277            bone_data = CNV_OT_export_cm3d2_model.bone_data_parser(CNV_OT_export_cm3d2_model.indexed_data_generator(target, prefix="BoneData:"))
278            local_bone_data = CNV_OT_export_cm3d2_model.local_bone_data_parser(CNV_OT_export_cm3d2_model.indexed_data_generator(target, prefix="LocalBoneData:"))
279        
280        old_basis = ob.matrix_basis.copy()
281        if bone_data:
282            self.from_bone_data(ob, bone_data, local_bone_data, base_bone_name, self.scale)
283        else:
284            self.from_armature(ob, arm_ob.data, base_bone_name)
285
286        if self.is_preserve_mesh:
287            new_basis = ob.matrix_basis.copy()
288            ob.matrix_basis = compat.mul(new_basis.inverted(), old_basis)
289            bpy.ops.object.transform_apply(location=True, rotation=True, scale=False)
290            ob.matrix_basis = new_basis
291
292
293        return {'FINISHED'}
bl_idname = 'object.align_to_cm3d2_base_bone'
bl_label = 'Align to Base Bone'
bl_description = "Align the object to it's armature's base bone"
bl_options = {'REGISTER', 'UNDO'}
scale: <_PropertyDeferred, <built-in function FloatProperty>, {'name': 'Scale', 'default': 5, 'min': 0.1, 'max': 100, 'soft_min': 0.1, 'soft_max': 100, 'step': 100, 'precision': 1, 'description': 'The amount by which the mesh is scaled when imported. Recommended that you use the same when at the time of export.', 'attr': 'scale'}> = <_PropertyDeferred, <built-in function FloatProperty>, {'name': 'Scale', 'default': 5, 'min': 0.1, 'max': 100, 'soft_min': 0.1, 'soft_max': 100, 'step': 100, 'precision': 1, 'description': 'The amount by which the mesh is scaled when imported. Recommended that you use the same when at the time of export.', 'attr': 'scale'}>
is_preserve_mesh: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Preserve Mesh', 'default': True, 'description': 'Align object transform, then fix mesh transform so it remains in place.', 'attr': 'is_preserve_mesh'}> = <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Preserve Mesh', 'default': True, 'description': 'Align object transform, then fix mesh transform so it remains in place.', 'attr': 'is_preserve_mesh'}>
items = [('ARMATURE', 'Armature', '', 'OUTLINER_OB_ARMATURE', 1), ('TEXT', 'Text', '', 'FILE_TEXT', 2), ('OBJECT_PROPERTY', 'Object Data', '', 'OBJECT_DATAMODE', 3), ('ARMATURE_PROPERTY', 'Armature Data', '', 'ARMATURE_DATA', 4)]
bone_info_mode: <_PropertyDeferred, <built-in function EnumProperty>, {'items': [('ARMATURE', 'Armature', '', 'OUTLINER_OB_ARMATURE', 1), ('TEXT', 'Text', '', 'FILE_TEXT', 2), ('OBJECT_PROPERTY', 'Object Data', '', 'OBJECT_DATAMODE', 3), ('ARMATURE_PROPERTY', 'Armature Data', '', 'ARMATURE_DATA', 4)], 'name': 'Bone Data Source', 'default': 'OBJECT_PROPERTY', 'description': 'This will decide from where the Bone Data is gathered from.', 'attr': 'bone_info_mode'}> = <_PropertyDeferred, <built-in function EnumProperty>, {'items': [('ARMATURE', 'Armature', '', 'OUTLINER_OB_ARMATURE', 1), ('TEXT', 'Text', '', 'FILE_TEXT', 2), ('OBJECT_PROPERTY', 'Object Data', '', 'OBJECT_DATAMODE', 3), ('ARMATURE_PROPERTY', 'Armature Data', '', 'ARMATURE_DATA', 4)], 'name': 'Bone Data Source', 'default': 'OBJECT_PROPERTY', 'description': 'This will decide from where the Bone Data is gathered from.', 'attr': 'bone_info_mode'}>
@staticmethod
def find_base_bone(ob: bpy_types.Object):
 91    @staticmethod
 92    def find_base_bone(ob: bpy.types.Object):
 93        arm_ob = ob.find_armature()
 94        if (not arm_ob) and (ob.parent and ob.parent.type == 'ARMATURE'):
 95            arm_ob = ob.parent
 96        
 97        base_bone_name = None
 98        if arm_ob:
 99            base_bone_name = arm_ob.data.get('BaseBone')
100        if not base_bone_name:
101            base_bone_name = ob.data.get('BaseBone')
102        if not base_bone_name:
103            # TODO : Check for base bone in object name
104            # See model_export.CNV_OT_export_cm3d2_model.export() "BoneData情報読み込み"
105            pass
106        
107        return base_bone_name
@classmethod
def poll(cls, context):
110    @classmethod
111    def poll(cls, context):
112        ob = context.object
113        if not ob: 
114            return False
115        return cls.find_base_bone(ob) != None
def invoke(self, context, event):
117    def invoke(self, context, event):
118        ob = context.object
119
120        # model名とか
121        #ob_names = common.remove_serial_number(ob.name, self.is_arrange_name).split('.')
122        #self.model_name = ob_names[0]
123        #self.base_bone_name = ob_names[1] if len(ob_names) >= 2  else 'Auto'
124
125        # ボーン情報元のデフォルトオプションを取得
126        if "BoneData" in context.blend_data.texts:
127            self.bone_info_mode = 'TEXT'
128        if "BoneData:0" in ob:
129            self.bone_info_mode = 'OBJECT_PROPERTY'
130        arm_ob = ob.find_armature()
131        if (not arm_ob) and (ob.parent and ob.parent.type == 'ARMATURE'):
132            arm_ob = ob.parent
133        if arm_ob:
134            if "BoneData:0" in arm_ob.data:
135                self.bone_info_mode = 'ARMATURE_PROPERTY'
136
137        self.scale = common.preferences().scale
138        return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
140    def draw(self, context):
141        ob = context.object
142        arm_ob = ob.find_armature()
143        if (not arm_ob) and (ob.parent and ob.parent.type == 'ARMATURE'):
144            arm_ob = ob.parent
145
146        def _prop_enum_row(layout, data, prop, value, enabled=True):
147            row = layout.row(align=True)
148            name = row.enum_item_name(data, prop, value)
149            icon = row.enum_item_icon(data, prop, value)
150            row.prop_enum(data, prop, value, text=name)
151            row.enabled = enabled
152            return row
153        
154        self.layout.prop(self, 'scale')
155        self.layout.prop(self, 'is_preserve_mesh', icon=compat.icon('MESH_DATA'))
156
157        col = self.layout.column(align=True)
158        col.label(text="Bone Data Source", icon='BONE_DATA')
159        _prop_enum_row(col, self, 'bone_info_mode', 'ARMATURE'         , enabled=bool(arm_ob                                ))
160        _prop_enum_row(col, self, 'bone_info_mode', 'TEXT'             , enabled=bool("BoneData" in context.blend_data.texts))
161        _prop_enum_row(col, self, 'bone_info_mode', 'OBJECT_PROPERTY'  , enabled=bool("BoneData:0" in ob                    ))
162        _prop_enum_row(col, self, 'bone_info_mode', 'ARMATURE_PROPERTY', enabled=bool(arm_ob and "BoneData:0" in arm_ob.data))
@staticmethod
def from_bone_data( ob: bpy_types.Object, bone_data, local_bone_data, base_bone_name, scale=5):
164    @staticmethod
165    def from_bone_data(ob: bpy.types.Object, bone_data, local_bone_data, base_bone_name, scale=5):
166        
167        base_bone_offset = mathutils.Matrix.Identity(4)
168        for bone in local_bone_data:
169            if bone['name'] == base_bone_name:
170                # When the base bone is in the bind pose data, 
171                # then the entire mesh needs to be offset from the base bone
172                print("Found base bone in local bone data!")
173                print(bone['matrix'])
174                mat = mathutils.Matrix(np.array(bone['matrix']).reshape((4,4)))
175                mat.transpose()
176                mat.translation *= -scale
177                mat.translation = compat.mul(mat.to_3x3().inverted(), mat.translation)
178                pos = mat.translation.copy()
179                
180                mat.transpose()
181                mat.translation = pos
182                #mat.row[3] = (0.0, 0.0, 0.0, 1.0)
183
184                base_bone_offset = mat
185
186        for bone in bone_data:
187            if bone['name'] == base_bone_name:
188                #co = bone['co'].copy()
189                #co.x, co.y, co.z = -co.x, -co.z, co.y
190                #co *= self.scale
191                #ob.location = co
192                #
193                #rot = bone['rot'].copy()
194                #eul = mathutils.Euler((math.radians(90), 0, 0), 'XYZ')
195                #rot.rotate(eul)
196                #ob.rotation_mode = 'QUATERNION'
197                #ob.rotation_quaternion = rot
198
199                parent_mats = []
200                current_bone = bone
201                while current_bone:
202                    local_co_mat  = mathutils.Matrix.Translation(mathutils.Vector(current_bone['co']) * scale)
203                    local_rot_mat = mathutils.Quaternion(current_bone['rot']).to_matrix().to_4x4()        
204                    parent_mats.append(compat.mul(local_co_mat, local_rot_mat))
205                    if current_bone.get('parent_name'):
206                        for b in bone_data:
207                            if b['name'] == current_bone['parent_name']:
208                                current_bone = b
209                                break
210                    elif current_bone.get('parent_index', -1) != -1 :
211                        current_bone = bone_data[current_bone['parent_index']]
212                    else:
213                        current_bone = None
214
215                parent_mats.reverse()
216                mat = mathutils.Matrix()
217                for local_mat in parent_mats:
218                    mat = compat.mul(mat, local_mat)
219
220                mat = compat.mul(mat, base_bone_offset.inverted())
221
222                mat = compat.convert_cm_to_bl_space(mat)
223                mat = compat.convert_cm_to_bl_local_space(mat)
224                ob.matrix_basis = mat
225                break
@staticmethod
def from_armature(ob: bpy_types.Object, arm: bpy.types.Armature, base_bone_name):
228    @staticmethod
229    def from_armature(ob: bpy.types.Object, arm: bpy.types.Armature, base_bone_name):
230        base_bone = arm.bones.get(base_bone_name)
231        mat = base_bone.matrix_local.copy()
232        mat = compat.convert_bl_to_cm_bone_rotation(mat)
233        mat = compat.convert_cm_to_bl_local_space(mat)
234        ob.matrix_basis = mat
def bone_data_report_cancel(self):
237    def bone_data_report_cancel(self):
238        source_name = self.bl_rna.properties['bone_info_mode']        \
239                      and source_name.enum_items[self.bone_info_mode] \
240                      and source_name.name                            \
241                      or self.bone_info_mode
242        self.report(
243            type    = {'ERROR'},
244            message = f_tip_(
245                "Could not find 'BaseBone' in {source_name} Please add it or change source", 
246                source_name = source_name
247            )
248        )
249        return {'CANCELLED'}
def execute(self, context):
252    def execute(self, context):
253        ob = context.object
254        arm_ob = ob.find_armature()
255        if (not arm_ob) and (ob.parent and ob.parent.type == 'ARMATURE'):
256            arm_ob = ob.parent
257        
258        base_bone_name = None
259        bone_data = None
260        if self.bone_info_mode == 'ARMATURE':
261            #bone_data = CNV_OT_export_cm3d2_model.armature_bone_data_parser(context, arm_ob)
262            if not 'BaseBone' in arm_ob.data:
263                return bone_data_report_cancel()
264            base_bone_name = arm_ob.data['BaseBone']
265        if self.bone_info_mode == 'TEXT':
266            bone_data_text = context.blend_data.texts["BoneData"]
267            if not 'BaseBone' in bone_data_text:
268                return bone_data_report_cancel()
269            base_bone_name = bone_data_text['BaseBone']
270            bone_data = CNV_OT_export_cm3d2_model.bone_data_parser(l.body for l in bone_data_text.lines)
271            local_bone_data = CNV_OT_export_cm3d2_model.local_bone_data_parser(l.body for l in bone_data_text.lines)
272        elif self.bone_info_mode in ['OBJECT_PROPERTY', 'ARMATURE_PROPERTY']:
273            target = ob if self.bone_info_mode == 'OBJECT_PROPERTY' else arm_ob.data
274            if not 'BaseBone' in target:
275                return bone_data_report_cancel()
276            base_bone_name = target['BaseBone']
277            bone_data = CNV_OT_export_cm3d2_model.bone_data_parser(CNV_OT_export_cm3d2_model.indexed_data_generator(target, prefix="BoneData:"))
278            local_bone_data = CNV_OT_export_cm3d2_model.local_bone_data_parser(CNV_OT_export_cm3d2_model.indexed_data_generator(target, prefix="LocalBoneData:"))
279        
280        old_basis = ob.matrix_basis.copy()
281        if bone_data:
282            self.from_bone_data(ob, bone_data, local_bone_data, base_bone_name, self.scale)
283        else:
284            self.from_armature(ob, arm_ob.data, base_bone_name)
285
286        if self.is_preserve_mesh:
287            new_basis = ob.matrix_basis.copy()
288            ob.matrix_basis = compat.mul(new_basis.inverted(), old_basis)
289            bpy.ops.object.transform_apply(location=True, rotation=True, scale=False)
290            ob.matrix_basis = new_basis
291
292
293        return {'FINISHED'}
bl_rna = <bpy_struct, Struct("OBJECT_OT_align_to_cm3d2_base_bone")>
Inherited Members
bpy_types.Operator
as_keywords
poll_message_set
builtins.bpy_struct
keys
values
get
pop
as_pointer
keyframe_insert
keyframe_delete
driver_add
driver_remove
is_property_set
property_unset
is_property_hidden
is_property_readonly
is_property_overridable_library
property_overridable_library_set
path_resolve
path_from_id
type_recast
bl_rna_get_subclass_py
bl_rna_get_subclass
id_properties_ensure
id_properties_clear
id_properties_ui
id_data